﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Index;
using System.Collections.Specialized;
using System.Collections;
using System.Security.Cryptography;
using Lucandra.Convenience;

namespace Lucandra.Net
{
    internal sealed class LucandraTermInfo
    {
        List<LucandraTermInfoDocument> documents;

        public string IndexName { get; private set; }
        public string FieldName { get; private set; }
        public string TermText { get; private set; }
        public int Frequency { get; private set; }
        public IEnumerable<LucandraTermInfoDocument> Documents
        {
            get { return this.documents; }
        }
        public int DocumentCount
        {
            get { return this.documents.Count; }
        }

        public LucandraTermInfo(string indexName, string fieldName, string termText)
        {
            this.IndexName = indexName;
            this.FieldName = fieldName;
            this.TermText = termText;
            this.Frequency = 0;

            this.documents = new List<LucandraTermInfoDocument>();
        }

        public void IncrementFrequency()
        {
            this.Frequency++;
        }

        public void AddTermInfoDocument(LucandraTermInfoDocument doc)
        {
            this.documents.Add(doc);
        }

        public Term GetTerm()
        {
            return new Term(this.FieldName, this.TermText);
        }

        public string GetTermString()
        {
            return this.FieldName + Settings.Delimeter + this.TermText;
        }

        public string GetTermKey(bool useKeyspacePerIndex)
        {
            return LucandraTermInfo.CreateTermKey(this.IndexName, this.FieldName, this.TermText, useKeyspacePerIndex);
        }

        //public string GetTermKey(string indexName)
        //{
        //    return indexName + LucandraHelper.Delimeter + this.GetTermString();
        //}

        public static LucandraTermInfo CreateFromTermString(string indexName, string termString)
        {
            int delimeterIndex = termString.IndexOf(Settings.Delimeter, StringComparison.Ordinal);

            return new LucandraTermInfo(
                indexName,
                termString.Substring(0, delimeterIndex),
                termString.Substring(delimeterIndex + Settings.Delimeter.Length));
        }

        public static LucandraTermInfo CreateFromTerm(string indexName, Term term)
        {
            return new LucandraTermInfo(indexName, term.Field(), term.Text());
        }

        /// <summary>
        /// Parse a TermInfo from the term key (as stored in the TermInfo column family: IndexName/FieldName/TermText).
        /// </summary>
        /// <param name="termKey">The term key (IndexName/FieldName/TermText).</param>
        /// <returns>A TermInfo object.</returns>
        public static LucandraTermInfo CreateFromTermKey(string termKey)
        {
            int hashLength = 10;

            int delimeterLength = Settings.Delimeter.Length;
            int indexNameDelimeterIndex = termKey.IndexOf(Settings.Delimeter, StringComparison.Ordinal);
            int fieldNameDelimeterIndex = termKey.LastIndexOf(Settings.Delimeter, StringComparison.Ordinal);

            string indexName = null;
            string fieldName;

            if (Settings.UseKeyspacePerIndex)
            {
                fieldName = termKey.Substring(hashLength, indexNameDelimeterIndex - hashLength);
            }
            else
            {
                indexName = termKey.Substring(hashLength, indexNameDelimeterIndex - hashLength);
                fieldName = termKey.Substring(indexNameDelimeterIndex + delimeterLength, fieldNameDelimeterIndex - indexNameDelimeterIndex - delimeterLength);
            }

            return new LucandraTermInfo(
                indexName,
                fieldName,
                termKey.Substring(fieldNameDelimeterIndex + delimeterLength));
        }

        public static string CreateTermKey(string indexName, string fieldName, string termText, bool useKeyspacePerIndex)
        {
            //return indexName + Settings.Delimeter + fieldName + Settings.Delimeter + termText;
            byte[] hash = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes((useKeyspacePerIndex ? fieldName : (indexName + fieldName))));
            byte[] newHash = new byte[10];
            Array.Copy(hash, newHash, newHash.Length);
            string hashString = LucandraHelper.CreateHash(newHash);

            if (useKeyspacePerIndex)
            {
                return string.Format("{0}{1}{2}{3}",
                    hashString, fieldName, Settings.Delimeter, termText);
            }
            else
            {
                return string.Format("{0}{1}{2}{3}{4}{5}",
                    hashString, indexName, Settings.Delimeter, fieldName, Settings.Delimeter, termText);
            }
        }

        public static string CreateTermString(string fieldName, string termText)
        {
            return fieldName + Settings.Delimeter + termText;
        }

        public override string ToString()
        {
            return string.Format("{{Index: `{0}`, Field: `{1}`, Text: `{2}`}}",
                this.IndexName, this.FieldName, this.TermText);
        }

        #region Nested Classes

        public class LucandraTermInfoDocument
        {
            List<int> positions;
            List<int> offsets;

            public string DocumentID { get; private set; }
            public int Frequency { get; private set; }
            public IEnumerable<int> Positions
            {
                get { return this.positions; }
            }
            public int PositionCount
            {
                get { return this.positions.Count; }
            }
            public IEnumerable<int> Offsets
            {
                get { return this.offsets; }
            }
            public int OffsetCount
            {
                get { return this.offsets.Count; }
            }
            public byte? NormalizationFactor { get; private set; }
            public bool UseNormalizationFactor
            {
                get { return this.NormalizationFactor.HasValue; }
            }
            public bool StorePositions { get; set; }
            public bool StoreOffsets { get; set; }

            #region Constructors

            public LucandraTermInfoDocument(string documentID)
                : this(documentID, 0, false, false, null, null, null)
            {
            }

            public LucandraTermInfoDocument(string documentID, int frequency, bool storePositions, bool storeOffsets, byte? normalizationFactor, IEnumerable<int> positions, IEnumerable<int> offsets)
            {
                this.DocumentID = documentID;
                this.Frequency = frequency;
                this.NormalizationFactor = normalizationFactor;
                this.StorePositions = storePositions;
                this.StoreOffsets = storeOffsets;

                this.positions = new List<int>(positions ?? new int[0]);
                this.offsets = new List<int>(offsets ?? new int[0]);
            }

            #endregion

            //public string GetDocumentKey(LucandraTermInfo termInfo)
            //{
                //return termInfo.IndexName + Settings.Delimeter + this.DocumentID;
            //}

            public void AddPosition(int position)
            {
                this.positions.Add(position);
            }

            public void IncrementFrequency()
            {
                this.Frequency++;
            }

            public void AddOffset(int offset)
            {
                this.offsets.Add(offset);
            }

            public void SetNormalizationFactor(byte? norm)
            {
                this.NormalizationFactor = norm;
            }

            public byte[] GetValueForStorage()
            {
                var byteBuffer = new ByteBuffer(64);

                // We'll store all of the informational flags in a single byte.
                byte flags = 0;
                if (this.NormalizationFactor.HasValue)
                    flags |= 1;
                if (this.StorePositions)
                    flags |= 2;
                if (this.StoreOffsets)
                    flags |= 4;

                // Write the flags to the buffer.
                byteBuffer.WriteByte(flags);

                // If norms are enabled, write the normalization factor byte to the buffer.
                if (this.NormalizationFactor.HasValue)
                    byteBuffer.WriteByte(this.NormalizationFactor.Value);

                // Write the frequency to the buffer.
                byteBuffer.WriteVariableInt32(this.Frequency);

                // If positions are stored, write each position to the buffer.
                if (this.StorePositions)
                {
                    for (int i = 0; i < this.positions.Count; i++)
                        byteBuffer.WriteVariableInt32(this.positions[i]);
                }

                // If offsets are stored, write each offset to the buffer.
                if (this.StoreOffsets)
                {
                    byteBuffer.WriteVariableInt32(this.offsets.Count);
                    for (int i = 0; i < this.offsets.Count; i++)
                        byteBuffer.WriteVariableInt32(this.offsets[i]);
                }

                // Return the raw bytes in the buffer.
                return byteBuffer.GetBytes();
            }

            /// <summary>
            /// Parses a Cassandra column (name/value) into a TermInfoDocument.
            /// </summary>
            /// <param name="documentID">The document ID.  This is without the IndexName as we know the IndexName from the TermInfo.</param>
            /// <param name="value">The value as is stored in Cassandra.</param>
            /// <returns>A TermInfoDocument.</returns>
            public static LucandraTermInfoDocument FromCassandraKeyValue(string documentID, byte[] value)
            {
                var byteBuffer = new ByteBuffer(value);

                byte flags = byteBuffer.ReadByte();
                bool useNorm = (flags & 1) != 0;
                bool storePositions = (flags & 2) != 0;
                bool storeOffsets = (flags & 4) != 0;

                byte? norm = null;
                if (useNorm)
                    norm = byteBuffer.ReadByte();

                int frequency = byteBuffer.ReadVariableInt32();

                List<int> positions = new List<int>();
                List<int> offsets = new List<int>();

                if (storePositions)
                {
                    for (int i = 0; i < frequency; i++)
                        positions.Add(byteBuffer.ReadVariableInt32());
                }

                if (storeOffsets)
                {
                    int offsetCount = byteBuffer.ReadVariableInt32();
                    for (int i = 0; i < offsetCount; i++)
                        offsets.Add(byteBuffer.ReadVariableInt32());
                }

                return new LucandraTermInfoDocument(documentID, frequency, storePositions, storeOffsets, norm, positions, offsets);
            }

            public override string ToString()
            {
                return string.Format("{{Document ID: `{0}`, Norm: {1}, Frequency: {2}, Positions: {3}, Offsets: {4}}}",
                    this.DocumentID,
                    this.NormalizationFactor,
                    this.Frequency,
                    this.StorePositions ? "[" + string.Join("-", this.Positions) + "]" : "Not Stored",
                    this.StoreOffsets ? "[" + string.Join("-", this.Offsets) + "]" : "Not Stored");
            }
        }

        #endregion
    }
}
